const { User, KycSubmission, KycDocument, Session, sequelize } = require('../models');
const { NotFoundError, BadRequestError } = require('../utils/errors');
const config = require('../config');

class KycService {
  getLevelOrder() {
    return ['none', 'level1', 'level2', 'level3', 'level4'];
  }

  getRequirementsByLevel(currentLevel) {
    const requirements = {
      none: [
        {
          id: 'email',
          name: 'Email Verification',
          description: 'Verify your email address',
        },
      ],
      level1: [
        {
          id: 'id_document',
          name: 'ID Document',
          description: 'Upload government-issued ID',
        },
        {
          id: 'selfie',
          name: 'Selfie Verification',
          description: 'Upload selfie with ID',
        },
      ],
      level2: [
        {
          id: 'address_proof',
          name: 'Proof of Address',
          description: 'Upload utility bill or bank statement',
        },
      ],
      level3: [
        {
          id: 'source_of_funds',
          name: 'Source of Funds',
          description: 'Provide documentation of income source',
        },
      ],
      level4: [],
    };

    return requirements[currentLevel] || [];
  }

  getNextLevel(currentLevel) {
    const levels = this.getLevelOrder();
    const currentIndex = levels.indexOf(currentLevel);

    if (currentIndex === -1 || currentIndex >= levels.length - 1) {
      return null;
    }

    const nextLevel = levels[currentIndex + 1];
    const limits = config.verificationLimits?.[nextLevel] || {};

    return {
      level: nextLevel,
      limits,
      requirements: this.getRequirementsByLevel(currentLevel),
    };
  }

  normalizeStatus(status) {
    if (!status) return 'not_started';

    switch (String(status).toLowerCase()) {
      case 'approved':
      case 'verified':
      case 'completed':
        return 'verified';
      case 'pending':
      case 'resubmitted':
      case 'under_review':
      case 'in_progress':
        return 'pending';
      case 'rejected':
      case 'failed':
        return 'rejected';
      default:
        return 'not_started';
    }
  }

  mapDocumentTypeName(type) {
    const map = {
      passport: 'Passport',
      national_id: 'National ID',
      drivers_license: "Driver's License",
      selfie: 'Selfie With ID',
      utility_bill: 'Utility Bill',
      bank_statement: 'Bank Statement',
      government_letter: 'Government Letter',
      source_of_funds: 'Source of Funds',
    };

    return map[type] || type;
  }

  getDocumentTypes() {
    return [
      { id: 'passport', name: 'Passport' },
      { id: 'national_id', name: 'National ID' },
      { id: 'drivers_license', name: "Driver's License" },
      { id: 'selfie', name: 'Selfie With ID' },
      { id: 'utility_bill', name: 'Utility Bill' },
      { id: 'bank_statement', name: 'Bank Statement' },
      { id: 'government_letter', name: 'Government Letter' },
      { id: 'source_of_funds', name: 'Source of Funds' },
    ];
  }

  getAddressDocumentTypes() {
    return ['Utility Bill', 'Bank Statement', 'Government Letter'];
  }

  mapSteps(user, submissions = []) {
    const byStep = new Map(submissions.map((item) => [item.step, item]));

    const identitySubmission =
      byStep.get('selfie')?.status === 'approved'
        ? byStep.get('selfie')
        : byStep.get('id_document');

    const addressSubmission = byStep.get('address_proof');
    const enhancedSubmission =
      byStep.get('enhanced_due_diligence') || byStep.get('source_of_funds');

    return [
      {
        step: 1,
        key: 'email',
        title: 'Email Verification',
        description: 'Verify your email address',
        status: user.emailVerified ? 'verified' : 'not_started',
      },
      {
        step: 2,
        key: 'identity',
        title: 'Identity Verification',
        description: 'Upload government-issued ID',
        status: this.normalizeStatus(identitySubmission?.status),
      },
      {
        step: 3,
        key: 'address',
        title: 'Address Verification',
        description: 'Proof of residential address',
        status: this.normalizeStatus(addressSubmission?.status),
      },
      {
        step: 4,
        key: 'enhanced',
        title: 'Enhanced Verification',
        description: 'Source of funds documentation',
        status: this.normalizeStatus(enhancedSubmission?.status),
      },
    ];
  }

  determineKycLevel(user, submissions = []) {
    const approvedSteps = new Set(
      submissions
        .filter((item) => item.status === 'approved')
        .map((item) => item.step)
    );

    const hasEmail = !!user.emailVerified;
    const hasIdentity = approvedSteps.has('id_document') || approvedSteps.has('selfie');
    const hasAddress = approvedSteps.has('address_proof');
    const hasEnhanced =
      approvedSteps.has('source_of_funds') || approvedSteps.has('enhanced_due_diligence');

    if (hasEmail && hasIdentity && hasAddress && hasEnhanced) {
      return 'level4';
    }

    if (hasEmail && hasIdentity && hasAddress) {
      return 'level3';
    }

    if (hasEmail && hasIdentity) {
      return 'level2';
    }

    if (hasEmail) {
      return 'level1';
    }

    return 'none';
  }

  determineOverallStatus(user, submissions = []) {
    if (!user.emailVerified && !submissions.length) {
      return 'not_started';
    }

    if (submissions.some((item) => item.status === 'rejected')) {
      return 'rejected';
    }

    if (
      submissions.some((item) =>
        ['pending', 'resubmitted'].includes(item.status)
      )
    ) {
      return 'pending';
    }

    const approvedSteps = new Set(
      submissions
        .filter((item) => item.status === 'approved')
        .map((item) => item.step)
    );

    const fullyApproved =
      user.emailVerified &&
      (approvedSteps.has('id_document') || approvedSteps.has('selfie')) &&
      approvedSteps.has('address_proof') &&
      (approvedSteps.has('source_of_funds') || approvedSteps.has('enhanced_due_diligence'));

    if (fullyApproved) {
      return 'approved';
    }

    return 'not_started';
  }

  buildVerificationLevels(steps = []) {
    const stepMap = new Map(steps.map((item) => [item.key, item]));

    const emailStatus = stepMap.get('email')?.status || 'not_started';
    const identityStatus = stepMap.get('identity')?.status || 'not_started';
    const addressStatus = stepMap.get('address')?.status || 'not_started';
    const enhancedStatus = stepMap.get('enhanced')?.status || 'not_started';

    return [
      {
        level: 1,
        name: 'Basic',
        status:
          emailStatus === 'verified'
            ? 'completed'
            : emailStatus === 'pending'
            ? 'in_progress'
            : 'not_started',
        requirements: ['Email Verification'],
        limits: {
          dailyWithdrawal: config.verificationLimits?.level1?.daily || 2000,
          monthlyWithdrawal: config.verificationLimits?.level1?.monthly || 10000,
          fiatDeposit: false,
          p2pTrading: false,
        },
      },
      {
        level: 2,
        name: 'Intermediate',
        status:
          identityStatus === 'verified'
            ? 'completed'
            : identityStatus === 'pending'
            ? 'in_progress'
            : 'not_started',
        requirements: ['Government ID', 'Selfie Verification'],
        limits: {
          dailyWithdrawal: config.verificationLimits?.level2?.daily || 50000,
          monthlyWithdrawal: config.verificationLimits?.level2?.monthly || 200000,
          fiatDeposit: true,
          p2pTrading: true,
        },
      },
      {
        level: 3,
        name: 'Advanced',
        status:
          addressStatus === 'verified'
            ? 'completed'
            : addressStatus === 'pending'
            ? 'in_progress'
            : 'not_started',
        requirements: ['Proof of Address', 'Source of Funds'],
        limits: {
          dailyWithdrawal: config.verificationLimits?.level3?.daily || 500000,
          monthlyWithdrawal: config.verificationLimits?.level3?.monthly || 2000000,
          fiatDeposit: true,
          p2pTrading: true,
        },
      },
      {
        level: 4,
        name: 'VIP',
        status:
          enhancedStatus === 'verified'
            ? 'completed'
            : enhancedStatus === 'pending'
            ? 'in_progress'
            : 'locked',
        requirements: ['Enhanced Due Diligence', 'Video Verification'],
        limits: {
          dailyWithdrawal: config.verificationLimits?.level4?.daily || 'Unlimited',
          monthlyWithdrawal: config.verificationLimits?.level4?.monthly || 'Unlimited',
          fiatDeposit: true,
          p2pTrading: true,
        },
      },
    ];
  }

  buildCurrentLimits(user) {
    const limits =
      typeof user.getVerificationLimits === 'function'
        ? user.getVerificationLimits()
        : config.verificationLimits?.[user.kycLevel] || {};

    return {
      dailyWithdrawal: {
        used: 0,
        limit: Number(limits?.daily || 0),
      },
      monthlyWithdrawal: {
        used: 0,
        limit: Number(limits?.monthly || 0),
      },
      dailyTrades: {
        used: 0,
        limit: limits?.dailyTrades || 'Unlimited',
      },
    };
  }

  mapDocuments(documents = []) {
    return documents.map((doc) => ({
      id: doc.id,
      name: this.mapDocumentTypeName(doc.documentType),
      type: doc.verificationType,
      verificationType: doc.verificationType,
      documentType: doc.documentType,
      status: this.normalizeStatus(doc.status),
      url: doc.fileUrl,
      fileUrl: doc.fileUrl,
      previewUrl: doc.fileUrl,
      documentUrl: doc.fileUrl,
      date: doc.submittedAt || doc.createdAt,
      submittedAt: doc.submittedAt || doc.createdAt,
      createdAt: doc.createdAt,
      note: doc.metadata?.note || null,
      mimeType: doc.mimeType || null,
      fileSize: doc.fileSize || null,
    }));
  }

  async getRecentActivity(userId) {
    const items = [];

    try {
      if (Session) {
        const sessions = await Session.findAll({
          where: { userId },
          order: [['createdAt', 'DESC']],
          limit: 6,
        });

        for (const session of sessions) {
          items.push({
            id: `session_${session.id}`,
            action: 'Login session',
            title: 'Login session',
            status: 'logged',
            ip: session.ip || 'System',
            location: session.location || 'Unknown location',
            device: session.device || 'Unknown device',
            createdAt: session.createdAt,
          });
        }
      }
    } catch (error) {
      // ignore if Session model is not available
    }

    const recentDocs = await KycDocument.findAll({
      where: { userId },
      order: [['createdAt', 'DESC']],
      limit: 6,
    });

    for (const doc of recentDocs) {
      items.push({
        id: `doc_${doc.id}`,
        action: `Uploaded ${this.mapDocumentTypeName(doc.documentType)}`,
        title: `Uploaded ${this.mapDocumentTypeName(doc.documentType)}`,
        status: this.normalizeStatus(doc.status),
        ip: 'System',
        location: 'Verification Center',
        device: 'Web',
        createdAt: doc.createdAt,
      });
    }

    return items
      .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
      .slice(0, 10);
  }

  async getUserOrThrow(userId, options = {}) {
    const user = await User.findByPk(userId, options);
    if (!user) {
      throw new NotFoundError('User not found');
    }
    return user;
  }

  async upsertSubmission(userId, step, payload = {}, transaction = null) {
    const [submission, created] = await KycSubmission.findOrCreate({
      where: { userId, step },
      defaults: {
        userId,
        step,
        status: 'pending',
        submittedAt: new Date(),
        metadata: payload.metadata || {},
        reviewNote: null,
      },
      transaction,
    });

    if (!created) {
      await submission.update(
        {
          status: submission.status === 'rejected' ? 'resubmitted' : 'pending',
          submittedAt: new Date(),
          reviewNote: null,
          reviewedAt: null,
          reviewedBy: null,
          metadata: {
            ...(submission.metadata || {}),
            ...(payload.metadata || {}),
          },
        },
        { transaction }
      );
    }

    return created ? submission : submission.reload({ transaction });
  }

  async recalculateUserKyc(userId, transaction = null) {
    const user = await this.getUserOrThrow(userId, { transaction });

    const submissions = await KycSubmission.findAll({
      where: { userId },
      transaction,
    });

    const nextLevel = this.determineKycLevel(user, submissions);
    const overallStatus = this.determineOverallStatus(user, submissions);

    const updatePayload = {
      kycLevel: nextLevel,
      kycStatus: overallStatus,
    };

    if (overallStatus === 'pending') {
      updatePayload.kycSubmittedAt = new Date();
      updatePayload.kycRejectedAt = null;
      updatePayload.kycReviewNote = null;
    }

    if (overallStatus === 'approved') {
      updatePayload.kycApprovedAt = new Date();
      updatePayload.kycRejectedAt = null;
    }

    if (overallStatus === 'rejected') {
      updatePayload.kycRejectedAt = new Date();
    }

    await user.update(updatePayload, { transaction });

    return user.reload({ transaction });
  }

  async getVerificationCenter(userId) {
    const user = await this.getUserOrThrow(userId);

    const submissions = await KycSubmission.findAll({
      where: { userId },
      include: [
        {
          model: KycDocument,
          as: 'documents',
        },
      ],
      order: [['createdAt', 'ASC']],
    });

    const documents = await KycDocument.findAll({
      where: { userId },
      order: [['createdAt', 'DESC']],
    });

    const steps = this.mapSteps(user, submissions);

    const verificationStatus = {
      email: steps.find((s) => s.key === 'email')?.status || 'not_started',
      identity: steps.find((s) => s.key === 'identity')?.status || 'not_started',
      address: steps.find((s) => s.key === 'address')?.status || 'not_started',
      enhanced: steps.find((s) => s.key === 'enhanced')?.status || 'not_started',
    };

    return {
      verification: {
        emailStatus: verificationStatus.email,
        identityStatus: verificationStatus.identity,
        addressStatus: verificationStatus.address,
        enhancedStatus: verificationStatus.enhanced,
      },
      verificationStatus,
      verificationLevels: this.buildVerificationLevels(steps),
      verificationSteps: steps,
      verificationDocuments: this.mapDocuments(documents),
      currentLimits: this.buildCurrentLimits(user),
      documentTypes: this.getDocumentTypes(),
      addressDocumentTypes: this.getAddressDocumentTypes(),
      loginlog: await this.getRecentActivity(userId),
    };
  }

  async getKycStatus(userId) {
    return this.getVerificationCenter(userId);
  }

  async submitBasicInfo(userId, info) {
    return sequelize.transaction(async (transaction) => {
      const user = await this.getUserOrThrow(userId, { transaction });

      if (!user.emailVerified) {
        throw new BadRequestError('Please verify your email first');
      }

      await user.update(
        {
          firstName: info.firstName || user.firstName,
          lastName: info.lastName || user.lastName,
          country: info.country || user.country,
          city: info.city || user.city,
          dateOfBirth: info.dateOfBirth || user.dateOfBirth || null,
          addressLine1: info.addressLine1 || user.addressLine1 || null,
          addressLine2: info.addressLine2 || user.addressLine2 || null,
          state: info.state || user.state || null,
          postalCode: info.postalCode || user.postalCode || null,
        },
        { transaction }
      );

      await this.upsertSubmission(
        userId,
        'basic_info',
        {
          metadata: {
            firstName: info.firstName,
            lastName: info.lastName,
            country: info.country,
            city: info.city,
          },
        },
        transaction
      );

      await this.recalculateUserKyc(userId, transaction);

      return {
        status: 'pending',
        message: 'Basic information submitted successfully',
      };
    });
  }

  async submitDocument(userId, payload) {
    const {
      step,
      verificationType,
      documentType,
      fileUrl,
      fileName,
      mimeType,
      fileSize,
      metadata,
    } = payload;

    if (!step) {
      throw new BadRequestError('step is required');
    }

    if (!verificationType) {
      throw new BadRequestError('verificationType is required');
    }

    if (!documentType) {
      throw new BadRequestError('documentType is required');
    }

    if (!fileUrl) {
      throw new BadRequestError('fileUrl is required');
    }

    return sequelize.transaction(async (transaction) => {
      await this.getUserOrThrow(userId, { transaction });

      const allowedSteps = [
        'id_document',
        'selfie',
        'address_proof',
        'source_of_funds',
        'enhanced_due_diligence',
      ];

      if (!allowedSteps.includes(step)) {
        throw new BadRequestError('Invalid document step');
      }

      const submission = await this.upsertSubmission(
        userId,
        step,
        { metadata },
        transaction
      );

      const document = await KycDocument.create(
        {
          userId,
          submissionId: submission.id,
          verificationType,
          documentType,
          fileUrl,
          fileName: fileName || null,
          mimeType: mimeType || null,
          fileSize: fileSize || null,
          status: 'pending',
          submittedAt: new Date(),
          metadata: metadata || {},
        },
        { transaction }
      );

      if (step === 'source_of_funds') {
        await this.upsertSubmission(
          userId,
          'enhanced_due_diligence',
          { metadata: { requestedByUser: true } },
          transaction
        );
      }

      await this.recalculateUserKyc(userId, transaction);

      return {
        status: 'pending',
        message: 'Document submitted for review',
        submissionId: submission.id,
        documentId: document.id,
      };
    });
  }

  async submitIdDocument(userId, documentData) {
    return this.submitDocument(userId, {
      step: 'id_document',
      verificationType: 'identity',
      documentType: documentData.type,
      fileUrl: documentData.url,
      fileName: documentData.fileName,
      mimeType: documentData.mimeType,
      fileSize: documentData.fileSize,
      metadata: documentData.metadata,
    });
  }

  async submitSelfie(userId, selfieData) {
    return this.submitDocument(userId, {
      step: 'selfie',
      verificationType: 'selfie',
      documentType: 'selfie',
      fileUrl: selfieData.url,
      fileName: selfieData.fileName,
      mimeType: selfieData.mimeType,
      fileSize: selfieData.fileSize,
      metadata: selfieData.metadata,
    });
  }

  async submitAddressProof(userId, proofData) {
    return this.submitDocument(userId, {
      step: 'address_proof',
      verificationType: 'address',
      documentType: proofData.type,
      fileUrl: proofData.url,
      fileName: proofData.fileName,
      mimeType: proofData.mimeType,
      fileSize: proofData.fileSize,
      metadata: proofData.metadata,
    });
  }

  async submitSourceOfFunds(userId, proofData) {
    return this.submitDocument(userId, {
      step: 'source_of_funds',
      verificationType: 'enhanced',
      documentType: proofData.type,
      fileUrl: proofData.url,
      fileName: proofData.fileName,
      mimeType: proofData.mimeType,
      fileSize: proofData.fileSize,
      metadata: proofData.metadata,
    });
  }

  async resendEmailVerification(userId) {
    const user = await this.getUserOrThrow(userId);

    if (user.emailVerified) {
      throw new BadRequestError('Email already verified');
    }

    return {
      email: user.email,
      sent: true,
    };
  }

  async requestEnhancedReview(userId) {
    return sequelize.transaction(async (transaction) => {
      const user = await this.getUserOrThrow(userId, { transaction });

      const submissions = await KycSubmission.findAll({
        where: { userId },
        transaction,
      });

      const approvedSteps = new Set(
        submissions
          .filter((item) => item.status === 'approved')
          .map((item) => item.step)
      );

      const hasIdentity =
        approvedSteps.has('id_document') || approvedSteps.has('selfie');

      if (!hasIdentity) {
        throw new BadRequestError('Complete identity verification first');
      }

      const submission = await this.upsertSubmission(
        userId,
        'enhanced_due_diligence',
        {
          metadata: {
            requestedFrom: 'verification_center',
          },
        },
        transaction
      );

      await this.recalculateUserKyc(userId, transaction);

      return {
        status: submission.status,
        step: submission.step,
        userId: user.id,
      };
    });
  }

  async getDocuments(userId) {
    await this.getUserOrThrow(userId);

    return KycDocument.findAll({
      where: { userId },
      include: [
        {
          model: KycSubmission,
          as: 'submission',
        },
      ],
      order: [['createdAt', 'DESC']],
    });
  }

  async approveSubmission({ submissionId, reviewerId, reviewNote = null }) {
    return sequelize.transaction(async (transaction) => {
      const submission = await KycSubmission.findByPk(submissionId, {
        include: [{ model: User, as: 'user' }],
        transaction,
      });

      if (!submission) {
        throw new NotFoundError('KYC submission not found');
      }

      await submission.update(
        {
          status: 'approved',
          reviewedAt: new Date(),
          reviewedBy: reviewerId || null,
          reviewNote,
        },
        { transaction }
      );

      await KycDocument.update(
        {
          status: 'approved',
          reviewedAt: new Date(),
          reviewedBy: reviewerId || null,
          reviewNote,
        },
        {
          where: {
            submissionId: submission.id,
            status: ['pending', 'resubmitted'],
          },
          transaction,
        }
      );

      await this.recalculateUserKyc(submission.userId, transaction);

      return {
        status: 'approved',
        message: 'Submission approved successfully',
      };
    });
  }

  async rejectSubmission({ submissionId, reviewerId, reviewNote }) {
    return sequelize.transaction(async (transaction) => {
      const submission = await KycSubmission.findByPk(submissionId, {
        transaction,
      });

      if (!submission) {
        throw new NotFoundError('KYC submission not found');
      }

      await submission.update(
        {
          status: 'rejected',
          reviewedAt: new Date(),
          reviewedBy: reviewerId || null,
          reviewNote: reviewNote || 'Submission rejected',
        },
        { transaction }
      );

      await KycDocument.update(
        {
          status: 'rejected',
          reviewedAt: new Date(),
          reviewedBy: reviewerId || null,
          reviewNote: reviewNote || 'Submission rejected',
        },
        {
          where: {
            submissionId: submission.id,
            status: ['pending', 'resubmitted'],
          },
          transaction,
        }
      );

      const user = await this.recalculateUserKyc(submission.userId, transaction);
      await user.update(
        {
          kycReviewNote: reviewNote || 'Submission rejected',
        },
        { transaction }
      );

      return {
        status: 'rejected',
        message: 'Submission rejected successfully',
      };
    });
  }
}

module.exports = new KycService();