import 'package:intl/intl.dart';
import 'package:uuid/uuid.dart';

import '../models/invoice_models.dart';
import '../models/sales_entry_models.dart';
import 'database_helper.dart';
import 'sales_entry_repository.dart';

class SalesEntryService {
  SalesEntryService({
    SalesEntryRepository? salesEntryRepository,
    DatabaseHelper? databaseHelper,
  })  : _salesEntryRepository = salesEntryRepository ?? SalesEntryRepository(),
        _dbHelper = databaseHelper ?? DatabaseHelper();

  final SalesEntryRepository _salesEntryRepository;
  final DatabaseHelper _dbHelper;
  final Uuid _uuid = const Uuid();
  final DateFormat _numberDateFormat = DateFormat('yyyyMMdd');

  Future<List<SalesEntry>> fetchEntries({SalesEntryStatus? status, int? limit}) {
    return _salesEntryRepository.fetchEntries(status: status, limit: limit);
  }

  Future<SalesEntry?> findById(String id) {
    return _salesEntryRepository.findById(id);
  }

  Future<void> deleteEntry(String id) {
    return _salesEntryRepository.deleteEntry(id);
  }

  Future<SalesEntry> saveEntry(SalesEntry entry) async {
    final recalculated = entry.recalcTotals().copyWith(updatedAt: DateTime.now());
    await _salesEntryRepository.upsertEntry(recalculated);
    return recalculated;
  }

  Future<List<SalesImportCandidate>> fetchImportCandidates({
    String? keyword,
    Set<DocumentType>? documentTypes,
    DateTime? startDate,
    DateTime? endDate,
    bool includeDrafts = false,
  }) async {
    final db = await _dbHelper.database;
    final whereClauses = <String>[];
    final args = <Object?>[];

    if (!includeDrafts) {
      whereClauses.add('COALESCE(is_draft, 0) = 0');
    }
    if (keyword != null && keyword.trim().isNotEmpty) {
      whereClauses.add('(customer_formal_name LIKE ? OR subject LIKE ?)');
      final like = '%${keyword.trim()}%';
      args..add(like)..add(like);
    }
    if (startDate != null) {
      whereClauses.add('date >= ?');
      args.add(startDate.toIso8601String());
    }
    if (endDate != null) {
      whereClauses.add('date <= ?');
      args.add(endDate.toIso8601String());
    }
    if (documentTypes != null && documentTypes.isNotEmpty) {
      final placeholders = List.filled(documentTypes.length, '?').join(',');
      whereClauses.add('document_type IN ($placeholders)');
      args.addAll(documentTypes.map((d) => d.name));
    }

    final whereSql = whereClauses.isEmpty ? '' : 'WHERE ${whereClauses.join(' AND ')}';
    final rows = await db.rawQuery('''
      SELECT id, customer_formal_name, date, total_amount, document_type, terminal_id,
             subject, is_locked, chain_status, content_hash
      FROM invoices
      $whereSql
      ORDER BY date DESC, id DESC
      LIMIT 200
    ''', args);

    return rows.map((row) {
      final docTypeName = row['document_type'] as String? ?? DocumentType.invoice.name;
      final documentType = DocumentType.values.firstWhere(
        (type) => type.name == docTypeName,
        orElse: () => DocumentType.invoice,
      );
      final invoiceNumber = _buildInvoiceNumber(
        documentType,
        row['terminal_id'] as String? ?? 'T1',
        DateTime.parse(row['date'] as String),
        row['id'] as String,
      );
      final map = {
        'id': row['id'],
        'invoice_number': invoiceNumber,
        'document_type': documentType.name,
        'date': row['date'],
        'customer_name': row['customer_formal_name'],
        'total_amount': row['total_amount'],
        'is_locked': row['is_locked'],
        'chain_status': row['chain_status'],
        'content_hash': row['content_hash'],
        'subject': row['subject'],
      };
      return SalesImportCandidate.fromMap(map);
    }).toList();
  }

  Future<SalesEntry> createEntryFromInvoices(List<String> invoiceIds, {String? subject, DateTime? issueDate}) async {
    if (invoiceIds.isEmpty) {
      throw ArgumentError('invoiceIds must not be empty');
    }
    final invoices = await _loadInvoiceData(invoiceIds);
    if (invoices.isEmpty) {
      throw StateError('指定された伝票が見つかりませんでした');
    }
    final now = DateTime.now();
    final entryId = _uuid.v4();
    final built = _buildEntryFromInvoices(
      invoices: invoices,
      entryId: entryId,
      baseEntry: null,
      subjectOverride: subject,
      issueDateOverride: issueDate,
      now: now,
    );
    await _salesEntryRepository.upsertEntry(built.entry);
    await _salesEntryRepository.upsertSources(built.entry.id, built.sources);
    return built.entry;
  }

  Future<SalesEntry> reimportEntry(String salesEntryId) async {
    final existing = await _salesEntryRepository.findById(salesEntryId);
    if (existing == null) {
      throw StateError('sales entry not found: $salesEntryId');
    }
    final sources = await _salesEntryRepository.fetchSources(salesEntryId);
    if (sources.isEmpty) {
      throw StateError('再インポート対象の元伝票が登録されていません');
    }
    final invoiceIds = sources.map((s) => s.invoiceId).toList();
    final invoices = await _loadInvoiceData(invoiceIds);
    if (invoices.isEmpty) {
      throw StateError('元伝票が見つかりませんでした');
    }
    final now = DateTime.now();
    final built = _buildEntryFromInvoices(
      invoices: invoices,
      entryId: existing.id,
      baseEntry: existing,
      now: now,
    );
    await _salesEntryRepository.upsertEntry(built.entry);
    await _salesEntryRepository.upsertSources(existing.id, built.sources);
    return built.entry;
  }

  _ImportBuildResult _buildEntryFromInvoices({
    required List<SalesInvoiceImportData> invoices,
    required String entryId,
    SalesEntry? baseEntry,
    String? subjectOverride,
    DateTime? issueDateOverride,
    required DateTime now,
  }) {
    final items = <SalesLineItem>[];
    for (final invoice in invoices) {
      for (final line in invoice.items) {
        items.add(
          SalesLineItem(
            id: _uuid.v4(),
            salesEntryId: entryId,
            description: '[${invoice.documentType.displayName}] ${line.description}',
            quantity: line.quantity,
            unitPrice: line.unitPrice,
            lineTotal: line.subtotal,
            productId: line.productId,
            taxRate: invoice.taxRate,
            sourceInvoiceId: invoice.invoiceId,
            sourceInvoiceItemId: line.id,
          ),
        );
      }
    }
    final issueDate = issueDateOverride ?? _latestDate(invoices) ?? baseEntry?.issueDate ?? now;
    final subject = subjectOverride ?? baseEntry?.subject ?? _deriveSubject(invoices);
    final customerId = _commonCustomerId(invoices) ?? baseEntry?.customerId;
    final customerNameSnapshot = _deriveCustomerSnapshot(invoices) ?? baseEntry?.customerNameSnapshot;

    final entry = (baseEntry ??
            SalesEntry(
              id: entryId,
              customerId: customerId,
              customerNameSnapshot: customerNameSnapshot,
              subject: subject,
              issueDate: issueDate,
              status: SalesEntryStatus.draft,
              notes: baseEntry?.notes,
              createdAt: baseEntry?.createdAt ?? now,
              updatedAt: now,
              items: items,
            ))
        .copyWith(
          customerId: customerId,
          customerNameSnapshot: customerNameSnapshot,
          subject: subject,
          issueDate: issueDate,
          status: baseEntry?.status ?? SalesEntryStatus.draft,
          items: items,
          updatedAt: now,
        )
        .recalcTotals();

    final sources = invoices
        .map(
          (inv) => SalesEntrySource(
            id: _uuid.v4(),
            salesEntryId: entry.id,
            invoiceId: inv.invoiceId,
            importedAt: now,
            invoiceHashSnapshot: inv.contentHash,
          ),
        )
        .toList();

    return _ImportBuildResult(entry: entry, sources: sources);
  }

  Future<List<SalesInvoiceImportData>> _loadInvoiceData(List<String> invoiceIds) async {
    if (invoiceIds.isEmpty) return [];
    final db = await _dbHelper.database;
    final placeholders = List.filled(invoiceIds.length, '?').join(',');
    final invoiceRows = await db.rawQuery('''
      SELECT id, customer_id, customer_formal_name, date, tax_rate, total_amount,
             document_type, subject, is_locked, chain_status, content_hash
      FROM invoices
      WHERE id IN ($placeholders)
    ''', invoiceIds);
    if (invoiceRows.isEmpty) return [];

    final itemRows = await db.query(
      'invoice_items',
      where: 'invoice_id IN ($placeholders)',
      whereArgs: invoiceIds,
    );
    final itemsByInvoice = <String, List<InvoiceItem>>{};
    for (final row in itemRows) {
      final invoiceId = row['invoice_id'] as String;
      final list = itemsByInvoice.putIfAbsent(invoiceId, () => []);
      list.add(InvoiceItem.fromMap(row));
    }

    return invoiceRows.map((row) {
      final docTypeName = row['document_type'] as String? ?? DocumentType.invoice.name;
      final documentType = DocumentType.values.firstWhere(
        (type) => type.name == docTypeName,
        orElse: () => DocumentType.invoice,
      );
      final invoiceId = row['id'] as String;
      return SalesInvoiceImportData(
        invoiceId: invoiceId,
        documentType: documentType,
        issueDate: DateTime.parse(row['date'] as String),
        taxRate: (row['tax_rate'] as num?)?.toDouble() ?? 0.1,
        totalAmount: (row['total_amount'] as num?)?.toInt() ?? 0,
        items: itemsByInvoice[invoiceId] ?? const [],
        isLocked: (row['is_locked'] as int?) == 1,
        chainStatus: row['chain_status'] as String? ?? 'pending',
        contentHash: row['content_hash'] as String? ?? '',
        customerId: row['customer_id'] as String?,
        customerFormalName: row['customer_formal_name'] as String?,
        subject: row['subject'] as String?,
      );
    }).toList();
  }

  String? _commonCustomerId(List<SalesInvoiceImportData> invoices) {
    final ids = invoices.map((e) => e.customerId).whereType<String>().toSet();
    if (ids.length == 1) return ids.first;
    return null;
  }

  String? _deriveCustomerSnapshot(List<SalesInvoiceImportData> invoices) {
    final names = invoices.map((e) => e.customerFormalName).whereType<String>().toSet();
    if (names.isEmpty) return null;
    if (names.length == 1) return names.first;
    return '複数取引先';
  }

  DateTime? _latestDate(List<SalesInvoiceImportData> invoices) {
    if (invoices.isEmpty) return null;
    return invoices.map((e) => e.issueDate).reduce((a, b) => a.isAfter(b) ? a : b);
  }

  String _deriveSubject(List<SalesInvoiceImportData> invoices) {
    if (invoices.isEmpty) return '売上伝票';
    if (invoices.length == 1) {
      return invoices.first.subject ?? '${invoices.first.documentType.displayName}取込';
    }
    return '複数伝票取込(${invoices.length}件)';
  }

  String _buildInvoiceNumber(DocumentType type, String terminalId, DateTime date, String id) {
    final suffix = id.length >= 4 ? id.substring(id.length - 4) : id;
    final datePart = _numberDateFormat.format(date);
    final prefix = _documentPrefix(type);
    return '$prefix-$terminalId-$datePart-$suffix';
  }

  String _documentPrefix(DocumentType type) {
    switch (type) {
      case DocumentType.estimation:
        return 'EST';
      case DocumentType.delivery:
        return 'DEL';
      case DocumentType.invoice:
        return 'INV';
      case DocumentType.receipt:
        return 'REC';
    }
  }
}

class _ImportBuildResult {
  const _ImportBuildResult({required this.entry, required this.sources});

  final SalesEntry entry;
  final List<SalesEntrySource> sources;
}
