import 'dart:io';
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'package:intl/intl.dart';
import '../models/hash_chain_models.dart';
import '../models/invoice_models.dart';
import '../models/customer_model.dart';
import '../models/customer_contact.dart';
import '../models/sales_summary.dart';
import 'database_helper.dart';
import 'activity_log_repository.dart';
import 'company_repository.dart';

class InvoiceRepository {
  final DatabaseHelper _dbHelper = DatabaseHelper();
  final ActivityLogRepository _logRepo = ActivityLogRepository();
  final CompanyRepository _companyRepo = CompanyRepository();

  Future<void> saveInvoice(Invoice invoice) async {
    final db = await _dbHelper.database;

    // 正式発行（下書きでない）場合はロックを掛ける
    final companyInfo = await _companyRepo.getCompanyInfo();
    String? sealHash;
    if (companyInfo.sealPath != null) {
      final file = File(companyInfo.sealPath!);
      if (await file.exists()) {
        sealHash = sha256.convert(await file.readAsBytes()).toString();
      }
    }
    final companySnapshot = jsonEncode({
      'name': companyInfo.name,
      'zipCode': companyInfo.zipCode,
      'address': companyInfo.address,
      'tel': companyInfo.tel,
      'fax': companyInfo.fax,
      'email': companyInfo.email,
      'url': companyInfo.url,
      'defaultTaxRate': companyInfo.defaultTaxRate,
      'taxDisplayMode': companyInfo.taxDisplayMode,
      'registrationNumber': companyInfo.registrationNumber,
    });

    final Invoice toSave = invoice.isDraft ? invoice : invoice.copyWith(isLocked: true);

    await db.transaction((txn) async {
      // 最新の連絡先をスナップショットする（なければ空）
      CustomerContact? activeContact;
      final contactRows = await txn.query('customer_contacts', where: 'customer_id = ? AND is_active = 1', whereArgs: [invoice.customer.id]);
      if (contactRows.isNotEmpty) {
        activeContact = CustomerContact.fromMap(contactRows.first);
      }
      final Invoice savingWithContact = toSave.copyWith(
        contactVersionId: activeContact?.version,
        contactEmailSnapshot: activeContact?.email,
        contactTelSnapshot: activeContact?.tel,
        contactAddressSnapshot: activeContact?.address,
        companySnapshot: companySnapshot,
        companySealHash: sealHash,
        metaJson: null,
        metaHash: null,
      );

      Invoice savingWithChain = savingWithContact;
      if (!savingWithContact.isDraft) {
        final previousEntry = await _fetchLatestChainEntry(txn);
        final previousHash = previousEntry?['chain_hash'] as String?;
        final computedHash = _computeChainHash(
          previousHash,
          savingWithContact.contentHash,
          savingWithContact.updatedAt,
          savingWithContact.id,
        );
        savingWithChain = savingWithContact.copyWith(
          previousChainHash: previousHash,
          chainHash: computedHash,
          chainStatus: 'pending',
        );
      } else {
        savingWithChain = savingWithContact.copyWith(
          previousChainHash: null,
          chainHash: null,
          chainStatus: 'draft',
        );
      }

      // 在庫の調整（更新の場合、以前の数量を戻してから新しい数量を引く）
      final List<Map<String, dynamic>> oldItems = await txn.query(
        'invoice_items',
        where: 'invoice_id = ?',
        whereArgs: [invoice.id],
      );

      // 旧在庫を戻す
      for (var item in oldItems) {
        if (item['product_id'] != null) {
          await txn.execute(
            'UPDATE products SET stock_quantity = stock_quantity + ? WHERE id = ?',
            [item['quantity'], item['product_id']],
          );
        }
      }

      // 伝票ヘッダーの保存
      await txn.insert(
        'invoices',
        savingWithChain.toMap(),
        conflictAlgorithm: ConflictAlgorithm.replace,
      );

      // 既存の明細を一旦削除
      await txn.delete(
        'invoice_items',
        where: 'invoice_id = ?',
        whereArgs: [invoice.id],
      );

      // 新しい明細の保存と在庫の減算
      for (var item in invoice.items) {
        await txn.insert('invoice_items', item.toMap(invoice.id));
        if (item.productId != null) {
          await txn.execute(
            'UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?',
            [item.quantity, item.productId],
          );
          if (!invoice.isDraft) {
            await txn.execute('UPDATE products SET is_locked = 1 WHERE id = ?', [item.productId]);
          }
        }
      }

      // 顧客をロック
      if (!invoice.isDraft) {
        await txn.execute('UPDATE customers SET is_locked = 1 WHERE id = ?', [invoice.customer.id]);
      }
    });

    await _logRepo.logAction(
      action: "SAVE_INVOICE",
      targetType: "INVOICE",
      targetId: invoice.id,
      details: "種別: ${invoice.documentTypeName}, 取引先: ${invoice.customerNameForDisplay}, 合計: ￥${invoice.totalAmount}",
    );
  }

  Future<void> updateInvoice(Invoice invoice) async {
    await saveInvoice(invoice);
  }

  Future<List<Invoice>> getAllInvoices(List<Customer> customers) async {
    final db = await _dbHelper.database;
    List<Map<String, dynamic>> invoiceMaps = await db.query('invoices', orderBy: 'date DESC');

    // サンプル自動投入（伝票が0件なら）
    if (invoiceMaps.isEmpty && customers.isNotEmpty) {
      await _generateSampleInvoices(customers.take(3).toList());
      invoiceMaps = await db.query('invoices', orderBy: 'date DESC');
    }

    List<Invoice> invoices = [];
    for (var iMap in invoiceMaps) {
      final customer = customers.firstWhere(
        (c) => c.id == iMap['customer_id'],
        orElse: () => Customer(id: iMap['customer_id'], displayName: "不明", formalName: "不明"),
      );

      final List<Map<String, dynamic>> itemMaps = await db.query(
        'invoice_items',
        where: 'invoice_id = ?',
        whereArgs: [iMap['id']],
      );

      final items = List.generate(itemMaps.length, (i) => InvoiceItem.fromMap(itemMaps[i]));

      // document_typeのパース
      DocumentType docType = DocumentType.invoice;
      if (iMap['document_type'] != null) {
        try {
          docType = DocumentType.values.firstWhere((e) => e.name == iMap['document_type']);
        } catch (_) {}
      }

      invoices.add(Invoice(
        id: iMap['id'],
        customer: customer,
        date: DateTime.parse(iMap['date']),
        items: items,
        notes: iMap['notes'],
        filePath: iMap['file_path'],
        taxRate: iMap['tax_rate'] ?? 0.10,
        documentType: docType,
        customerFormalNameSnapshot: iMap['customer_formal_name'],
        odooId: iMap['odoo_id'],
        isSynced: iMap['is_synced'] == 1,
        updatedAt: DateTime.parse(iMap['updated_at']),
        latitude: iMap['latitude'],
        longitude: iMap['longitude'],
        terminalId: iMap['terminal_id'] ?? "T1",
        isDraft: (iMap['is_draft'] ?? 0) == 1,
        subject: iMap['subject'],
        isLocked: (iMap['is_locked'] ?? 0) == 1,
        contactVersionId: iMap['contact_version_id'],
        contactEmailSnapshot: iMap['contact_email_snapshot'],
        contactTelSnapshot: iMap['contact_tel_snapshot'],
        contactAddressSnapshot: iMap['contact_address_snapshot'],
        companySnapshot: iMap['company_snapshot'],
        companySealHash: iMap['company_seal_hash'],
        metaJson: iMap['meta_json'],
        metaHash: iMap['meta_hash'],
        previousChainHash: iMap['previous_chain_hash'],
        chainHash: iMap['chain_hash'],
        chainStatus: iMap['chain_status'] ?? 'pending',
      ));
    }
    return invoices;
  }

  Future<void> _generateSampleInvoices(List<Customer> customers) async {
    if (customers.isEmpty) return;
    final now = DateTime.now();
    final items = [
      InvoiceItem(description: "商品A", quantity: 2, unitPrice: 1200),
      InvoiceItem(description: "商品B", quantity: 1, unitPrice: 3000),
    ];
    final List<Invoice> samples = List.generate(
      3,
      (i) => Invoice(
        customer: customers[i % customers.length],
        date: now.subtract(Duration(days: i * 3)),
        items: items,
        isDraft: i == 0, // 1件だけ下書き
        subject: "サンプル案件${i + 1}",
      ),
    );

    for (final inv in samples) {
      await saveInvoice(inv);
    }
  }

  Future<void> deleteInvoice(String id) async {
    final db = await _dbHelper.database;
    await db.transaction((txn) async {
      // 在庫の復元
      final List<Map<String, dynamic>> items = await txn.query(
        'invoice_items',
        where: 'invoice_id = ?',
        whereArgs: [id],
      );

      for (var item in items) {
        if (item['product_id'] != null) {
          await txn.execute(
            'UPDATE products SET stock_quantity = stock_quantity + ? WHERE id = ?',
            [item['quantity'], item['product_id']],
          );
        }
      }

      // PDFパスの取得（削除用）
      final List<Map<String, dynamic>> maps = await txn.query(
        'invoices',
        columns: ['file_path'],
        where: 'id = ?',
        whereArgs: [id],
      );
      
      if (maps.isNotEmpty && maps.first['file_path'] != null) {
        final file = File(maps.first['file_path']);
        if (await file.exists()) {
          await file.delete();
        }
      }

      await txn.delete(
        'invoice_items',
        where: 'invoice_id = ?',
        whereArgs: [id],
      );
      await txn.delete(
        'invoices',
        where: 'id = ?',
        whereArgs: [id],
      );
    });
  }

  Future<int> cleanupOrphanedPdfs() async {
    try {
      final directory = await getExternalStorageDirectory();
      if (directory == null) return 0;

      final files = directory.listSync().whereType<File>().toList();
      final db = await _dbHelper.database;
      final List<Map<String, dynamic>> results = await db.query('invoices', columns: ['file_path']);
      final activePaths = results.map((r) => r['file_path'] as String?).where((p) => p != null).toSet();

      int count = 0;
      for (var file in files) {
        if (file.path.endsWith('.pdf') && !activePaths.contains(file.path)) {
          await file.delete();
          count++;
        }
      }
      return count;
    } catch (e) {
      return 0;
    }
  }

  /// meta_json と meta_hash の整合性を検証する（trueなら一致）。
  bool verifyInvoiceMeta(Invoice invoice) {
    final metaJson = invoice.metaJson ?? invoice.metaJsonValue;
    final expected = sha256.convert(utf8.encode(metaJson)).toString();
    final stored = invoice.metaHash ?? expected;
    return expected == stored;
  }

  /// IDを指定してDBから取得し、メタデータ整合性を検証する。
  Future<bool> verifyInvoiceMetaById(String id, List<Customer> customers) async {
    final invoices = await getAllInvoices(customers);
    final target = invoices.firstWhere((i) => i.id == id, orElse: () => throw Exception('invoice not found'));
    return verifyInvoiceMeta(target);
  }

  Future<HashChainVerificationResult> verifyHashChain() async {
    final db = await _dbHelper.database;
    final now = DateTime.now();
    return await db.transaction((txn) async {
      final rows = await txn.query(
        'invoices',
        columns: [
          'id',
          'updated_at',
          'content_hash',
          'previous_chain_hash',
          'chain_hash',
          'document_type',
          'terminal_id',
          'date',
        ],
        where: 'COALESCE(is_draft, 0) = 0',
        orderBy: 'updated_at ASC, id ASC',
      );

      String? expectedPreviousHash;
      final breaks = <HashChainBreak>[];

      for (final row in rows) {
        final invoiceId = row['id'] as String;
        final invoiceNumber = _buildInvoiceNumberFromRow(row);
        final updatedAtStr = row['updated_at'] as String;
        final updatedAt = DateTime.parse(updatedAtStr);
        final contentHash = row['content_hash'] as String? ?? '';
        final actualPrev = row['previous_chain_hash'] as String?;
        final actualHash = row['chain_hash'] as String?;
        final expectedHash = _computeChainHash(expectedPreviousHash, contentHash, updatedAt, invoiceId);

        bool broken = false;
        if ((expectedPreviousHash ?? '') != (actualPrev ?? '')) {
          final info = HashChainBreak(
            invoiceId: invoiceId,
            invoiceNumber: invoiceNumber,
            issue: 'previous_hash_mismatch',
            expectedHash: expectedHash,
            actualHash: actualHash,
            expectedPreviousHash: expectedPreviousHash,
            actualPreviousHash: actualPrev,
          );
          breaks.add(info);
          await _logChainBreak(info);
          broken = true;
        }

        if (actualHash == null || actualHash != expectedHash) {
          final info = HashChainBreak(
            invoiceId: invoiceId,
            invoiceNumber: invoiceNumber,
            issue: actualHash == null ? 'hash_missing' : 'hash_mismatch',
            expectedHash: expectedHash,
            actualHash: actualHash,
            expectedPreviousHash: expectedPreviousHash,
            actualPreviousHash: actualPrev,
          );
          breaks.add(info);
          await _logChainBreak(info);
          broken = true;
        }

        await txn.update(
          'invoices',
          {'chain_status': broken ? 'broken' : 'healthy'},
          where: 'id = ?',
          whereArgs: [invoiceId],
        );

        expectedPreviousHash = expectedHash;
      }

      await _logRepo.logAction(
        action: 'HASH_CHAIN_VERIFY',
        targetType: 'INVOICE',
        details: jsonEncode({
          'checkedCount': rows.length,
          'breakCount': breaks.length,
          'verifiedAt': now.toIso8601String(),
        }),
      );

      return HashChainVerificationResult(
        isHealthy: breaks.isEmpty,
        checkedCount: rows.length,
        verifiedAt: now,
        breaks: breaks,
      );
    });
  }

  Future<Map<String, Object?>?> _fetchLatestChainEntry(DatabaseExecutor txn) async {
    final rows = await txn.query(
      'invoices',
      columns: ['chain_hash'],
      where: 'COALESCE(is_draft, 0) = 0 AND chain_hash IS NOT NULL',
      orderBy: 'updated_at DESC, id DESC',
      limit: 1,
    );
    if (rows.isEmpty) return null;
    return rows.first;
  }

  String _computeChainHash(String? previousHash, String contentHash, DateTime updatedAt, String id) {
    final payload = '${previousHash ?? ''}|$contentHash|${updatedAt.toIso8601String()}|$id';
    return sha256.convert(utf8.encode(payload)).toString();
  }

  Future<void> _logChainBreak(HashChainBreak info) async {
    await _logRepo.logAction(
      action: 'HASH_CHAIN_BREAK',
      targetType: 'INVOICE',
      targetId: info.invoiceId,
      details: jsonEncode({
        'issue': info.issue,
        'invoiceNumber': info.invoiceNumber,
        'expectedHash': info.expectedHash,
        'actualHash': info.actualHash,
        'expectedPreviousHash': info.expectedPreviousHash,
        'actualPreviousHash': info.actualPreviousHash,
      }),
    );
  }

  String _buildInvoiceNumberFromRow(Map<String, Object?> row) {
    final docTypeName = row['document_type'] as String? ?? DocumentType.invoice.name;
    DocumentType docType;
    try {
      docType = DocumentType.values.firstWhere((e) => e.name == docTypeName);
    } catch (_) {
      docType = DocumentType.invoice;
    }
    final prefix = _documentPrefix(docType);
    final terminalId = row['terminal_id'] as String? ?? 'T1';
    final dateStr = row['date'] as String? ?? row['updated_at'] as String;
    final date = DateTime.tryParse(dateStr) ?? DateTime.now();
    final id = row['id'] as String;
    final suffix = id.length >= 4 ? id.substring(id.length - 4) : id;
    final formatter = DateFormat('yyyyMMdd');
    return '$prefix-$terminalId-${formatter.format(date)}-$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';
    }
  }

  Future<SalesSummary> fetchSalesSummary({
    required int year,
    DocumentType? documentType,
    bool includeDrafts = false,
    int topCustomerLimit = 5,
  }) async {
    final db = await _dbHelper.database;
    final baseArgs = <Object?>[year.toString()];
    final whereBuffer = StringBuffer("strftime('%Y', date) = ?");
    if (documentType != null) {
      whereBuffer.write(" AND document_type = ?");
      baseArgs.add(documentType.name);
    }
    if (!includeDrafts) {
      whereBuffer.write(" AND COALESCE(is_draft, 0) = 0");
    }
    final whereClause = whereBuffer.toString();

    final monthlyRows = await db.rawQuery(
      '''
      SELECT CAST(strftime('%m', date) AS INTEGER) as month, SUM(total_amount) as total
      FROM invoices
      WHERE $whereClause
      GROUP BY month
      ORDER BY month ASC
      '''.
          trim(),
      List<Object?>.from(baseArgs),
    );

    final monthlyTotals = <int, int>{};
    for (final row in monthlyRows) {
      if (row['month'] == null || row['total'] == null) continue;
      monthlyTotals[(row['month'] as num).toInt()] = (row['total'] as num).toInt();
    }

    final yearlyTotal = monthlyTotals.values.fold<int>(0, (sum, value) => sum + value);

    final customerRows = await db.rawQuery(
      '''
      SELECT COALESCE(customer_formal_name, customer_id) as customer_name, SUM(total_amount) as total
      FROM invoices
      WHERE $whereClause
      GROUP BY customer_name
      ORDER BY total DESC
      LIMIT ?
      '''.
          trim(),
      [...baseArgs, topCustomerLimit],
    );

    final customerStats = customerRows
        .where((row) => row['customer_name'] != null && row['total'] != null)
        .map(
          (row) => SalesCustomerStat(
            customerName: row['customer_name'] as String,
            totalAmount: (row['total'] as num).toInt(),
          ),
        )
        .toList();

    return SalesSummary(
      year: year,
      documentType: documentType,
      monthlyTotals: monthlyTotals,
      yearlyTotal: yearlyTotal,
      customerStats: customerStats,
    );
  }

  Future<Map<String, int>> getMonthlySales(int year) async {
    final summary = await fetchSalesSummary(year: year);
    return summary.monthlyTotals.map((key, value) => MapEntry(key.toString().padLeft(2, '0'), value));
  }

  Future<int> getYearlyTotal(int year) async {
    final summary = await fetchSalesSummary(year: year);
    return summary.yearlyTotal;
  }
}
