import {
  Injectable,
  NotFoundException,
  BadRequestException,
  Logger,
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { NcpStorageService } from '../../common/services/ncp-storage.service';
import { UpdateBusinessDto } from './dto/update-business.dto';
import { RegisterDigitalCertDto } from './dto/register-digital-cert.dto';
import { RegisterHometaxDto } from './dto/register-hometax.dto';
import * as path from 'path';

@Injectable()
export class BusinessesService {
  private readonly logger = new Logger(BusinessesService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly ncpStorage: NcpStorageService,
  ) {}

  private serializeBigInt(data: any): any {
    return JSON.parse(
      JSON.stringify(data, (_, v) => (typeof v === 'bigint' ? Number(v) : v)),
    );
  }

  async getBusiness(userId: number) {
    const business = await this.prisma.business.findUnique({
      where: { userId },
    });

    if (!business) {
      throw new NotFoundException('Business not found');
    }

    const data = this.serializeBigInt(business);

    // Include user phone/email as fallback (from registration)
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { phone: true, email: true },
    });
    if (user) {
      data.userPhone = user.phone || '';
      data.userEmail = user.email || '';
    }

    return data;
  }

  async updateBusiness(userId: number, dto: UpdateBusinessDto) {
    const existing = await this.prisma.business.findUnique({
      where: { userId },
    });

    if (!existing) {
      throw new NotFoundException('Business not found');
    }

    const updated = await this.prisma.business.update({
      where: { userId },
      data: dto,
    });

    return this.serializeBigInt(updated);
  }

  async ocrBusinessCert(userId: number, file: Express.Multer.File) {
    // 1. Fetch user name for file naming
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { name: true },
    });
    const safeName = (user?.name || 'unknown')
      .replace(/[^a-zA-Z0-9가-힣]/g, '_')
      .substring(0, 30);

    // 2. Upload file to NCP Object Storage
    const ext = path.extname(file.originalname) || '.png';
    const key = `certs/${userId}_${safeName}_${Date.now()}${ext}`;
    const contentType = file.mimetype || 'application/octet-stream';

    const certUrl = await this.ncpStorage.upload(file.buffer, key, contentType);

    // 3. Extract text based on file type
    const isPdf = file.mimetype === 'application/pdf' || ext.toLowerCase() === '.pdf';
    let fullText = '';
    const apiKey = process.env.GOOGLE_VISION_API_KEY;

    if (isPdf) {
      // PDF: first try pdf-parse for text-based PDFs
      try {
        const pdfParseModule = require('pdf-parse');
        const pdfParseFn = pdfParseModule.default || pdfParseModule;
        const pdfData = await pdfParseFn(file.buffer);
        fullText = pdfData.text || '';
        this.logger.log(`pdf-parse text for user ${userId} (${fullText.length} chars)`);
      } catch (err) {
        this.logger.error('PDF parse error:', err);
      }

      // If pdf-parse returned too little text, use Google Vision files:annotate for scanned PDFs
      if (fullText.trim().length < 50 && apiKey) {
        this.logger.log(`pdf-parse insufficient (${fullText.trim().length} chars), trying Google Vision files:annotate`);
        try {
          const base64Pdf = file.buffer.toString('base64');
          const response = await fetch(
            `https://vision.googleapis.com/v1/files:annotate?key=${apiKey}`,
            {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'Referer': 'https://api-freeflow.officialsite.kr/',
              },
              body: JSON.stringify({
                requests: [
                  {
                    inputConfig: {
                      content: base64Pdf,
                      mimeType: 'application/pdf',
                    },
                    features: [{ type: 'DOCUMENT_TEXT_DETECTION' }],
                    pages: [1],
                  },
                ],
              }),
            },
          );

          const visionData = await response.json();
          if (visionData.error) {
            this.logger.error('Vision files:annotate error:', JSON.stringify(visionData.error));
          } else {
            // files:annotate returns nested responses per page
            const pageResponses = visionData.responses?.[0]?.responses || [];
            const texts = pageResponses
              .map((r: any) => r.fullTextAnnotation?.text || '')
              .filter((t: string) => t.length > 0);
            if (texts.length > 0) {
              fullText = texts.join('\n');
              this.logger.log(`Vision PDF OCR for user ${userId} (${fullText.length} chars)`);
            }
          }
        } catch (err) {
          this.logger.error('Vision files:annotate error:', err);
        }
      }
    } else {
      // Image: use Google Cloud Vision API OCR
      if (apiKey) {
        try {
          const base64Image = file.buffer.toString('base64');
          const response = await fetch(
            `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`,
            {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'Referer': 'https://api-freeflow.officialsite.kr/',
              },
              body: JSON.stringify({
                requests: [
                  {
                    image: { content: base64Image },
                    features: [{ type: 'TEXT_DETECTION' }],
                  },
                ],
              }),
            },
          );

          const visionData = await response.json();
          if (visionData.error) {
            this.logger.error('Google Vision API error:', JSON.stringify(visionData.error));
          } else {
            fullText = visionData.responses?.[0]?.textAnnotations?.[0]?.description || '';
          }
        } catch (err) {
          this.logger.error('Vision API error:', err);
        }
      }
    }

    this.logger.log(`OCR/PDF raw text for user ${userId} (${fullText.length} chars):\n${fullText.substring(0, 500)}`);

    // 4. Parse business registration certificate
    const ocrData = this.parseBusinessCert(fullText);
    this.logger.log(`Parsed ocrData for user ${userId}: ${JSON.stringify(ocrData)}`);

    // 5. Update business record
    const updateData: any = { registrationCertUrl: certUrl };
    if (ocrData.registrationNumber) updateData.registrationNumber = ocrData.registrationNumber;
    if (ocrData.businessName) updateData.businessName = ocrData.businessName;
    if (ocrData.ownerName) updateData.ownerName = ocrData.ownerName;
    if (ocrData.businessType) updateData.businessType = ocrData.businessType;
    if (ocrData.businessItem) updateData.businessItem = ocrData.businessItem;
    if (ocrData.address) updateData.address = ocrData.address;
    // phone/email are from user registration, not OCR — don't overwrite

    await this.prisma.business.update({
      where: { userId },
      data: updateData,
    });

    return { certUrl, ocrData };
  }

  /**
   * Parse Korean Business Registration Certificate (사업자등록증) text.
   * Handles both OCR output and PDF-extracted text.
   *
   * Key insight: In Korean certificates, labels have spaced characters
   * (e.g., 성 명, 대 표 자, 업 태) while actual values are written
   * as consecutive characters (e.g., 이서현, 브로시스터, 정보통신업).
   */
  private parseBusinessCert(text: string): Record<string, string> {
    if (!text || text.trim().length === 0) return {};

    const result: Record<string, string> = {};

    // Aggressive normalization: remove zero-width spaces, soft hyphens, BOM
    const normalized = text
      .replace(/\r\n/g, '\n')
      .replace(/[\u200B-\u200D\uFEFF\u00AD\u200E\u200F]/g, '')
      .replace(/\t/g, ' ');

    this.logger.log(`[PARSE] Normalized text (first 800):\n${normalized.substring(0, 800)}`);

    // Known label words to exclude when searching for names
    const LABEL_WORDS = [
      '성명', '대표자', '대표', '유형', '법인명', '법인', '명칭',
      '생년월일', '생년', '월일', '주민', '등록번호', '등록',
      '사업자', '사업장', '소재지', '업태', '종목', '상호',
      '일반과세자', '간이과세자', '면세사업자', '과세자',
      '개업', '연월일', '전자세금', '계산서', '세무서장',
      '국세청', '증명', '발급', '번호', '처리', '기간',
      '공동', '해당사항', '없습니다', '정보통신', '소매',
    ];

    const isLabelWord = (w: string) =>
      LABEL_WORDS.some(l => w === l || l.includes(w) || w.includes(l));

    // 1. Registration number: XXX-XX-XXXXX
    const regNumMatch = normalized.match(/(\d{3})\s*[-–—]\s*(\d{2})\s*[-–—]\s*(\d{5})/);
    if (regNumMatch) {
      result.registrationNumber = `${regNumMatch[1]}-${regNumMatch[2]}-${regNumMatch[3]}`;
    }

    // 2. Business name (상호/법인명)
    //    Find 상호 label, then extract text until next label
    const bizLabelMatch = normalized.match(
      /(?:상\s*호|법\s*인\s*명)\s*(?:\([^)]*\))?\s*[|:：]?\s*/m,
    );
    if (bizLabelMatch?.index !== undefined) {
      const startIdx = bizLabelMatch.index + bizLabelMatch[0].length;
      const afterLabel = normalized.substring(startIdx, startIdx + 200);
      // Take text until next label keyword or newline
      const nextLabelMatch = afterLabel.match(
        /\n|성\s*명|사\s*업\s*자|등\s*록\s*번|대\s*표|주\s*민|개\s*업/,
      );
      let name = nextLabelMatch
        ? afterLabel.substring(0, nextLabelMatch.index).trim()
        : afterLabel.split('\n')[0].trim();
      name = name.replace(/^[|·:：\s]+/, '').replace(/[|·:：\s]+$/, '').trim();
      if (name.length > 0 && name.length <= 50) {
        result.businessName = name;
      }
    }
    // Fallback 1: "호 : 브로시스터" (when 상/호 are split into separate blocks by OCR)
    if (!result.businessName) {
      const hoMatch = normalized.match(/호\s*[|:：]\s*([가-힣][가-힣a-zA-Z0-9\s]*)/m);
      if (hoMatch) {
        let name = hoMatch[1].trim();
        name = name.split(/\n/)[0].trim();
        if (name.length >= 2 && name.length <= 50) {
          result.businessName = name;
          this.logger.log(`[PARSE] businessName (호: fallback): ${name}`);
        }
      }
    }
    // Fallback 2: consecutive Korean chars near 상호
    if (!result.businessName) {
      const bizLabel = normalized.match(/상\s*호/);
      if (bizLabel?.index !== undefined) {
        const area = normalized.substring(bizLabel.index, bizLabel.index + 150);
        const words = area.match(/[가-힣]{2,}/g);
        for (const w of words || []) {
          if (!isLabelWord(w) && w.length >= 2) {
            result.businessName = w;
            break;
          }
        }
      }
    }
    this.logger.log(`[PARSE] businessName: ${result.businessName || '(empty)'}`);

    // 3. Owner name (성명/대표자)
    //    Key insight: labels have spaces between chars (성 명, 대 표 자)
    //    but names are consecutive Korean chars (이서현, 홍길동)
    const ownerLabel = normalized.match(/성\s*명|대\s*표\s*자/);
    if (ownerLabel?.index !== undefined) {
      const area = normalized.substring(ownerLabel.index, ownerLabel.index + 200);
      this.logger.log(`[PARSE] Owner search area: ${area.substring(0, 100)}`);

      // Find consecutive Korean char groups (2-5 chars = typical Korean person name)
      const koreanWords = area.match(/[가-힣]{2,5}/g);
      this.logger.log(`[PARSE] Korean words found: ${JSON.stringify(koreanWords)}`);

      for (const word of koreanWords || []) {
        if (!isLabelWord(word)) {
          result.ownerName = word;
          this.logger.log(`[PARSE] Owner name matched: ${word}`);
          break;
        }
      }
    }
    if (!result.ownerName) {
      this.logger.log(`[PARSE] Owner name NOT found. Trying broader search...`);
      // Broader fallback: search entire text for pattern "성명 ... Korean_name"
      const broadMatch = normalized.match(
        /(?:성\s*명|대\s*표\s*자)\s*(?:\([^)]*\))?\s*[|:：]?\s*([가-힣]{2,5})/,
      );
      if (broadMatch) {
        const name = broadMatch[1];
        if (!isLabelWord(name)) {
          result.ownerName = name;
          this.logger.log(`[PARSE] Owner name (broad): ${name}`);
        }
      }
    }
    // Fallback: "명 : 이서현" (when 성/명 are split into separate blocks by OCR column reading)
    if (!result.ownerName) {
      const myungMatch = normalized.match(/명\s*[|:：]\s*([가-힣]{2,5})/m);
      if (myungMatch && !isLabelWord(myungMatch[1])) {
        result.ownerName = myungMatch[1];
        this.logger.log(`[PARSE] Owner name (명: fallback): ${myungMatch[1]}`);
      }
    }

    // 4. Business type (업태)
    const typeMatch = normalized.match(/업\s*태\s*[|:：]?\s*(.+)/m);
    if (typeMatch) {
      let val = typeMatch[1].replace(/^[|·\s]+/, '').trim();
      val = val.split(/종\s*목/)[0].trim();
      val = val.split(/\s{3,}/)[0].trim();
      if (val.length > 1 && val.length <= 100) result.businessType = val;
    }
    // Fallback: column-split pattern "업\n정보통신업\n태" (OCR reads label chars separately)
    if (!result.businessType) {
      const splitType = normalized.match(/\n업\n([가-힣]{2,})\n태/);
      if (splitType) {
        result.businessType = splitType[1];
        this.logger.log(`[PARSE] businessType (column-split fallback): ${splitType[1]}`);
      }
    }

    // 5. Business item (종목)
    const itemMatch = normalized.match(/종\s*목\s*[|:：]?\s*(.+)/m);
    if (itemMatch) {
      let val = itemMatch[1].replace(/^[|·\s]+/, '').trim();
      val = val.split(/\s{3,}/)[0].trim();
      if (val.length > 1 && val.length <= 200) result.businessItem = val;
    }
    // Fallback: look for Korean business item description after 종/목 area
    if (!result.businessItem) {
      // Find lines after "목" that contain substantial Korean text (skip noise like E, REPUBLI)
      const mokIdx = normalized.search(/\n목\n/);
      if (mokIdx >= 0) {
        const afterMok = normalized.substring(mokIdx + 3);
        const lines = afterMok.split('\n');
        for (const line of lines) {
          const trimmed = line.trim();
          // Look for a line with 5+ Korean chars (actual business item description)
          if (trimmed.length >= 5 && /[가-힣].*[가-힣].*[가-힣]/.test(trimmed) && !isLabelWord(trimmed)) {
            result.businessItem = trimmed;
            this.logger.log(`[PARSE] businessItem (column-split fallback): ${trimmed}`);
            break;
          }
        }
      }
    }

    // 6. Address (사업장 소재지)
    const addrMatch = normalized.match(
      /(?:사\s*업\s*장\s*)?소\s*재\s*지\s*[|:：]?\s*(.+)/m,
    );
    if (addrMatch) {
      let addr = addrMatch[1].replace(/^[|·\s]+/, '').trim();
      addr = addr.split(/(?:업\s*태|종\s*목|전\s*화|개\s*업|사\s*업\s*자)/)[0].trim();
      if (addr.length > 5 && addr.length <= 300) {
        result.address = addr;
      }
    }

    // 7. Phone (전화번호/연락처)
    const phoneMatch = normalized.match(
      /(?:전\s*화(?:\s*번\s*호)?|연\s*락\s*처|TEL)\s*[|:：]?\s*([\d\-–().\s]+)/im,
    );
    if (phoneMatch) {
      const phone = phoneMatch[1].replace(/[–—]/g, '-').replace(/\s/g, '').trim();
      if (phone.length >= 8) result.phone = phone;
    }

    this.logger.log(`[PARSE] Final result: ${JSON.stringify(result)}`);
    return result;
  }

  async uploadCert(userId: number, certUrl: string) {
    const existing = await this.prisma.business.findUnique({
      where: { userId },
    });

    if (!existing) {
      throw new NotFoundException('Business not found');
    }

    const updated = await this.prisma.business.update({
      where: { userId },
      data: { registrationCertUrl: certUrl },
    });

    return this.serializeBigInt(updated);
  }

  async registerDigitalCert(userId: number, dto: RegisterDigitalCertDto) {
    const existing = await this.prisma.business.findUnique({
      where: { userId },
    });

    if (!existing) {
      throw new NotFoundException('Business not found');
    }

    const updated = await this.prisma.business.update({
      where: { userId },
      data: {
        digitalCertRegistered: true,
        digitalCertValidFrom: dto.validFrom
          ? new Date(dto.validFrom)
          : undefined,
        digitalCertValidUntil: dto.validUntil
          ? new Date(dto.validUntil)
          : undefined,
      },
    });

    return this.serializeBigInt(updated);
  }

  async registerHometax(userId: number, dto: RegisterHometaxDto) {
    const existing = await this.prisma.business.findUnique({
      where: { userId },
    });

    if (!existing) {
      throw new NotFoundException('Business not found');
    }

    const updated = await this.prisma.business.update({
      where: { userId },
      data: {
        hometaxAuthStatus: 'connected',
        hometaxAuthMethod: dto.authMethod,
        hometaxAuthRegisteredAt: new Date(),
      },
    });

    return this.serializeBigInt(updated);
  }

  async disconnectHometax(userId: number) {
    const existing = await this.prisma.business.findUnique({
      where: { userId },
    });

    if (!existing) {
      throw new NotFoundException('Business not found');
    }

    const updated = await this.prisma.business.update({
      where: { userId },
      data: {
        hometaxAuthStatus: 'unregistered',
        hometaxAuthMethod: null,
        hometaxAuthRegisteredAt: null,
      },
    });

    return this.serializeBigInt(updated);
  }
}
